/**
 * PerlaController - part of PerlaGis Architecture
 *
 * Copyright (C) 2010 Ricardo Gonzalez (gomnaz@gmail.com)
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see http://www.gnu.org/licenses/
 */
package org.perla.services;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.gis.services.NoSuchSensorException_Exception;
import org.notify.entities.Relation;
import org.notify.entities.RelationAction;
import org.notify.exceptions.NoRelationException;
import org.perla.managers.Node;
import org.perla.entities.Attitude;
import org.perla.entities.Bibi;
import org.perla.entities.Crackmeter;
import org.perla.entities.Sensori;
import org.perla.entities.Temperature;
import org.perla.entities.Tiposensore;
import org.perla.exceptions.NoSuchSensorException;
import org.perla.exceptions.WrongSensorTypeException;
import org.perla.managers.Sensor;
import org.perla.entities.Nodo;
import org.perla.managers.Gis;
import org.perla.managers.Notify;
import org.perla.managers.Value;

/**
 * Classe che rappresenta i servizi di amministrazione del PerlaController.
 * Implementa i metodi necessari a gestire le relazioni Perla-Gis e a conoscere
 * i sensori  * disponibili e le relative informazioni raggiungibili solo
 * dall'amminsitratore.
 *
 * @author Ricardo Gonzalez
 * @author Eva Gjeci
 */
@WebService()
public class Administration {

    @PersistenceContext(unitName = "PerlaController")
    private EntityManager em;
    @Resource
    private javax.transaction.UserTransaction utx;
    @PersistenceContext(unitName = "RelationController")
    private EntityManager emRelation;

    /**
     * Restituisce l'elenco completo dei sensori presenti nel database Perla.
     *
     * @return la lista dei sensori
     */
    @WebMethod(operationName = "getSensors")
    public List<Sensori> getSensors() {
        return Sensor.getAll(em);
    }

    /**
     * Restituisce l'elenco completo dei nodi presenti nel database Perla.
     *
     * @return la lista dei sensori
     */
    @WebMethod(operationName = "getNodes")
    public List<Nodo> getNodes() {
        return Node.getAll(em);
    }

    /**
     * Restituisce la lista dei sensori che appartengono al nodo passato come
     * parametro.
     *
     * @param idNodo la chiave del nodo
     * @return la lista dei sensori
     */
    @WebMethod(operationName = "getSensorsByNode")
    public List<Sensori> getSensorsByNode(@WebParam(name = "idNodo") String idNodo) {
        return Sensor.getByNode(idNodo, em);
    }

    /**
     * Restituisce la classe <code>Tiposensori</code> che rappresenta il tipo
     * del sensore identificato dalle chiavi passate come parametri.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @return il tipo del sensore
     * @throws NoSuchSensorException se il sensore identificato dalle chiavi non esiste
     */
    @WebMethod(operationName = "getSensorType")
    public Tiposensore getSensorType(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore) throws NoSuchSensorException {
        Tiposensore t = Sensor.get(idNodo, idSensore, em).getTiposensore();
        t.getSensoriList().clear(); //Cancella la lista dei sensori per un problema di ridondanza
        return t;
    }

    /**
     * Restituisce la lista dei sensori che appartengono ad un dato tipo.
     *
     * @param tipo il tipo del sensore
     * @return la lista dei sensori dello stesos tipo
     */
    @WebMethod(operationName = "getSensorsByType")
    public List<Sensori> getSensorsByType(@WebParam(name = "type") Tiposensore tipo) {
        List<Sensori> l = null;
        try {
            Tiposensore t = em.find(Tiposensore.class, tipo);
            l = t.getSensoriList();
            Sensor.clearListRedudancy(l);
        } catch (IllegalArgumentException e) {
            System.out.println("Sensor type error");
            throw e;
        }
        return l;
    }

    /**
     * Restituisce la lista degli ultimi valori di un sensore Attitude.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @param limit il numero massimo di valori da richiedere
     * @return la lista dei valori
     * @throws NoSuchSensorException se il sensore identificato dalle chiavi non esiste
     * @throws WrongSensorTypeException se il sensore identificato dalle chiavi non è di tipo Attitude
     */
    @WebMethod(operationName = "getAttitudeValues")
    public java.util.List<Attitude> getAttitudeValues(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore, @WebParam(name = "limit") int limit) throws WrongSensorTypeException, NoSuchSensorException {
        List<Attitude> l = Value.getAttitudes(Sensor.get(idNodo, idSensore, em), limit, em);
        return l;
    }

    /**
     * Restituisce la lista dei valori di un sensore Attitude registrati
     * nell'intervallo indicato.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @param from l'inizio dell'intervallo
     * @param to la fine dell'intervallo
     * @return la lista dei valori
     * @throws NoSuchSensorException se il sensore identificato dalle chiavi non esiste
     * @throws WrongSensorTypeException se il sensore identificato dalle chiavi non è di tipo Attitude
     */
    @WebMethod(operationName = "getAttitudeValuesByDate")
    public java.util.List<Attitude> getAttitudeValuesByDate(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore, @WebParam(name = "from") long from, @WebParam(name = "to") long to) throws WrongSensorTypeException, NoSuchSensorException {
        List<Attitude> l = Value.getAttitudes(Sensor.get(idNodo, idSensore, em), from, to, em);
        return l;
    }

    /**
     * Restituisce la lista dei valori di un sensore Bibi registrati
     * nell'intervallo indicato.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @param from l'inizio dell'intervallo
     * @param to la fine dell'intervallo
     * @return la lista dei valori
     * @throws NoSuchSensorException se il sensore identificato dalle chiavi non esiste
     * @throws WrongSensorTypeException se il sensore identificato dalle chiavi non è di tipo Bibi
     */
    @WebMethod(operationName = "getBibiValuesByDate")
    public java.util.List<Bibi> getBibiValuesByDate(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore, @WebParam(name = "from") long from, @WebParam(name = "to") long to) throws WrongSensorTypeException, NoSuchSensorException {
        List<Bibi> l = Value.getBibis(Sensor.get(idNodo, idSensore, em), from, to, em);
        return l;
    }

    /**
     * Restituisce la lista dei valori di un sensore Crackmeter registrati
     * nell'intervallo indicato.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @param from l'inizio dell'intervallo
     * @param to la fine dell'intervallo
     * @return la lista dei valori
     * @throws NoSuchSensorException se il sensore identificato dalle chiavi non esiste
     * @throws WrongSensorTypeException se il sensore identificato dalle chiavi non è di tipo Crackmeter
     */
    @WebMethod(operationName = "getCrackmeterValuesByDate")
    public java.util.List<Crackmeter> getCrackmeterValuesByDate(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore, @WebParam(name = "from") long from, @WebParam(name = "to") long to) throws WrongSensorTypeException, NoSuchSensorException {
        List<Crackmeter> l = Value.getCrackmeters(Sensor.get(idNodo, idSensore, em), from, to, em);
        return l;
    }

    /**
     * Restituisce la lista dei valori di un sensore Temperature registrati
     * nell'intervallo indicato.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @param from l'inizio dell'intervallo
     * @param to la fine dell'intervallo
     * @return la lista dei valori
     * @throws NoSuchSensorException se il sensore identificato dalle chiavi non esiste
     * @throws WrongSensorTypeException se il sensore identificato dalle chiavi non è di tipo Temperature
     */
    @WebMethod(operationName = "getTemperatureValuesByDate")
    public java.util.List<Temperature> getTemperatureValuesByDate(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore, @WebParam(name = "from") long from, @WebParam(name = "to") long to) throws WrongSensorTypeException, NoSuchSensorException {
        List<Temperature> l = Value.getTemperatures(Sensor.get(idNodo, idSensore, em), from, to, em);
        return l;
    }

    /**
     * Restituisce la lista degli ultimi valori di un sensore Bibi.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @param limit il numero massimo di valori da richiedere
     * @return la lista dei valori
     * @throws NoSuchSensorException se il sensore identificato dalle chiavi non esiste
     * @throws WrongSensorTypeException se il sensore identificato dalle chiavi non è di tipo Bibi
     */
    @WebMethod(operationName = "getBibiValues")
    public java.util.List<Bibi> getBibiValues(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore, @WebParam(name = "limit") int limit) throws NoSuchSensorException, WrongSensorTypeException {
        List<Bibi> l = Value.getBibis(Sensor.get(idNodo, idSensore, em), limit, em);
        return l;
    }

    /**
     * Restituisce la lista degli ultimi valori di un sensore Crackmeter.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @param limit il numero massimo di valori da richiedere
     * @return la lista dei valori
     * @throws NoSuchSensorException se il sensore identificato dalle chiavi non esiste
     * @throws WrongSensorTypeException se il sensore identificato dalle chiavi non è di tipo Crackmeter
     */
    @WebMethod(operationName = "getCrackmeterValues")
    public java.util.List<Crackmeter> getCrackmeterValues(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore, @WebParam(name = "limit") int limit) throws WrongSensorTypeException, NoSuchSensorException {
        List<Crackmeter> l = Value.getCrackmeters(Sensor.get(idNodo, idSensore, em), limit, em);
        return l;
    }

    /**
     * Restituisce la lista degli ultimi valori di un sensore Temperature.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @param limit il numero massimo di valori da richiedere
     * @return la lista dei valori
     * @throws NoSuchSensorException se il sensore identificato dalle chiavi non esiste
     * @throws WrongSensorTypeException se il sensore identificato dalle chiavi non è di tipo Temperature
     */
    @WebMethod(operationName = "getTemperatureValues")
    public java.util.List<Temperature> getTemperatureValues(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore, @WebParam(name = "limit") int limit) throws NoSuchSensorException, WrongSensorTypeException {
        List<Temperature> l = Value.getTemperatures(Sensor.get(idNodo, idSensore, em), limit, em);
        return l;
    }

    /**
     * Restituisce il sensore che corrisponde alle chiavi passate come parametri.
     *
     * @param idNodo la chiave del nodo
     * @param idSensore la chiave del sensore nel nodo
     * @throws NoSuchSensorException se non esiste un sensore per le chiavi passate
     */
    @WebMethod(operationName = "getSensor")
    public Sensori getSensor(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "idSensore") String idSensore) throws NoSuchSensorException {
        return Sensor.get(idNodo, idSensore, em);
    }

    /**
     * Crea la relazione Perla-Gis e invia gli ultimi valori del nodo al Gis.
     *
     * @param idNodo la chiave del nodo in Perla
     * @param nome il nome attribuito al sensore nel Gis
     * @param notify true se si vuole la notifica
     * @return true se l'inserimento è avvenuto correttamente, false altrimenti
     */
    @WebMethod(operationName = "addRelation")
    public boolean addRelation(@WebParam(name = "idNodo") String idNodo, @WebParam(name = "nome") String nome, @WebParam(name = "notify") boolean notify) {
        int idGis = Gis.addSensor(nome);
        if (idGis == -1) {
            return false;
        }
        Relation r = Notify.newRelation(idNodo, 9);
        r.setNotifica(notify);
        r.setIdSensoreGis(idGis);
        r.setLabelSensoreGis(nome);
        if (!saveRelation(r, RelationAction.ADD)){
            try {
                Gis.removeSensor(idGis);
            } catch (NoSuchSensorException_Exception ex) {
                Logger.getLogger(Administration.class.getName()).log(Level.SEVERE, null, ex);
            }
            return false;
        }
        try {
            Gis.sendUpdate(idNodo, em, emRelation);
        } catch (NoSuchSensorException ex) {
            Logger.getLogger(Administration.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        return true;
    }

    /**
     * Cambia lo stato della notifica per il sensore indicato.
     *
     * @param idGis la chiave del sensore Gis
     * @return il nuovo valore per la notifica
     */
    @WebMethod(operationName = "changeNotify")
    public boolean changeNotify(@WebParam(name = "idGis") String idGis) throws NoRelationException {
        Relation r = Notify.getRelation(idGis, emRelation);
        r.setNotifica(!r.getNotifica());
        saveRelation(r, RelationAction.UPDATE);
        return r.getNotifica();
    }

    /**
     * Aggiorna tutti i sensori del Gis con l'ultimo valore presente nel
     * database Perla.
     *
     * @return true se l'aggiornamento è avvenuto correttamente
     */
    @WebMethod(operationName = "updateAll")
    public boolean updateAll() {
        boolean e = true;
        List<Relation> l = Notify.getAll(emRelation);
        for (Relation r : l) {
            try {
                Gis.sendUpdate(r.getIdNodoPerla(), em, emRelation);
            } catch (NoSuchSensorException ex) {
                Logger.getLogger(Administration.class.getName()).log(Level.SEVERE, null, ex);
                e = false;
            }
        }
        return e;
    }

    /**
     * Restituisce la lista di tutte le relazioni Perla-Gis.
     *
     * @return la lista delle relazioni
     */
    @WebMethod(operationName = "getRelations")
    public List<Relation> getRelations() {
        return Notify.getAll(emRelation);
    }

    /**
     * Rimuove la relazione Perla-Gis indicata. Il parametro passato è la
     * chiave del sensore Gis.
     *
     * @param idGis la chiave del sensore Gis
     * @return true se l'operazione ha avuto successo
     */
    @WebMethod(operationName = "removeRelation")
    public boolean removeRelation(@WebParam(name = "idGis") String idGis) throws NoRelationException {
        Relation r = Notify.getRelation(idGis, emRelation);
        boolean result = true;
        if (saveRelation(r, RelationAction.REMOVE)) {
            try {
                Gis.removeSensor(Integer.parseInt(idGis));
            } catch (NoSuchSensorException_Exception ex) {
                Logger.getLogger(Administration.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            result = false;
        }
        return result;
    }

    /**
     * Esegue le operazioni ADD, UPDATE e REMOVE sul database Notify
     * relativamente all'oggetto <code>Relation</code> passato come parametro.
     *
     * @param object l'oggetto Relation
     * @param action l'azione da eseguire
     * @see RelationAction
     */
    private boolean saveRelation(Relation object, RelationAction action) {
        boolean result = true;
        try {
            utx.begin();
            emRelation.joinTransaction();
            if (action == RelationAction.ADD) {
                emRelation.persist(object);
            } else if (action == RelationAction.REMOVE) {
                Relation r0 = emRelation.merge(object);
                emRelation.remove(r0);
            } else if (action == RelationAction.UPDATE) {
                emRelation.merge(object);
            }
            utx.commit();
        } catch (Exception e) {
            Logger.getLogger(getClass().getName()).log(Level.SEVERE, "exception caught", e);
            result = false;
            throw new RuntimeException(e);
        } finally {
            return result;
        }
    }
}
